package netTracker.network;
import java.util.HashMap;
import java.io.*;
import java.net.*;

/**
 * This class runs as a thread and continuously listens for incoming messages.  When it
 * receives one, it handles it with the appropriate message handler.
 * @author Ari Sapon-White and David Cummings
 */
public class ReceiveWorker implements Runnable {
	/** A hash map of message handlers for handling different types of messages. */
	private HashMap<String, MsgHandler> messageHandlers;
	private PostOffice pobox;
	private ServerSocket ssock;
	private boolean done;
	
	/**
	 * Constructs a ReceiveWorker with no message handlers.
	 */
	public ReceiveWorker(PostOffice po)	throws IOException {	pobox = po;
		done = false;
		messageHandlers = new HashMap<String, MsgHandler>();
		ssock = new ServerSocket (pobox.getPort ());
	}
	
	/**
	 * Adds a new message handler to the hash map of message handlers.
	 * @param key the name of the message handler
	 * @param value the message handler to add
	 */
	public void addHandler(String key, MsgHandler myHandler){
		synchronized (messageHandlers) { 
			messageHandlers.put(key, myHandler); 
		}
	}
	
	/**
	 * Retrieves a specific message handler.
	 * @param key the name of the message handler to retrieve
	 * @return the requested message handler
	 */
	public MsgHandler getHandler(String key){	
		synchronized (messageHandlers) { return messageHandlers.get(key); }
	}
	
	/**
	 * Constantly listens for new messages and processes them by
	 * sending them to appropriate message handlers.
	 */
	public void run(){
		while(!done){	
			try{	
				Socket sock = ssock.accept ();
				ObjectInputStream ois = new ObjectInputStream (
					sock.getInputStream ());
				Message m = (Message)ois.readObject ();
				// Update host address of message:
				m.getSrc().setHostname(sock.getInetAddress().getHostAddress()); 
				ois.close ();
				(new Thread (new DeliveryHandler (m))).start ();
			}catch (Exception e){	
				System.err.println ("Error reading message: " + e); 
			}
		} // while
	} // run

	/**
	 * Internal class to handle deliveries.
	 */
	class DeliveryHandler implements Runnable{	
		Message msg;
		public DeliveryHandler (Message msg) { 
			this.msg = msg; 
		}
		/** 
		 * Handles a message by reading its tag and matching it
		 * to a registered message handler. If there is none,
		 * add it to the mailbox.
		 */
		public void run (){	
			MsgHandler handler = null;
			synchronized (messageHandlers){	
				handler = messageHandlers.get (msg.getTag()); 
			}
			if (handler != null){ 	// If there is a designated handler,
				try{				// have it handle the message.
					handler.handleMsg (msg);
				}catch(PostalException e){
					System.err.println(e);
				}
			}else{
				pobox.enqueueMsg (msg); // Otherwise put it in the mailbox.
			}
		} // run
	} // DeliveryHandler
} // class ReceiveWorker
